package folder.model.node;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map.Entry;

import org.apache.commons.math3.stat.descriptive.SummaryStatistics;

import folder.model.link.Link;
import folder.model.network.Network;
import folder.model.virtual.VirtualNode;

public class Node implements Comparable< Node >
{	
	// Node
	
	public static Node node( int identifier, Network network ){ return new Node( identifier, network ); }
	public Node( int identifier, Network network )
	{
		this.identifier = identifier;
		this.network = network;
	}
	
	private final int identifier;
	public int getIdentifier(){ return this.identifier; }
    public String toString(){ return Integer.toString( identifier ) + " " + this.getVirtualNodes(); }
	
	private double identifierRandom = Math.random();
	public double getIdentifierRandom(){ return this.identifierRandom; }
	public void setIdentifierRandom( int identifierRandom ){ this.identifierRandom = identifierRandom; }
	
	private final Network network;
	public Network getNetwork(){ return this.network; }
	
	private final List< Link > links = new ArrayList< Link >();
	public List< Link > getLinks(){ return this.links; }
	public void addLink( Link link ){ this.links.add( link ); }
	public void disconnectLinks()
	{
		for( Link link: links )
			link.disconnect();
	}
	public void connectLinks()
	{
		for( Link link: links )
			link.connect();
	}
	
	// Paths
	
	private double delayToNodeSource;
	public double getDelayToNodeSource(){ return this.delayToNodeSource; }
	public void setDelayToNodeSource( double delayToNodeSource ){ this.delayToNodeSource = delayToNodeSource; }
	public void resetDelayToNodeSource(){ this.delayToNodeSource = Double.POSITIVE_INFINITY; }
    public int compareTo( Node node ){ return Double.compare( delayToNodeSource, node.delayToNodeSource ); }
	
	private Node nodePrevious;
    public Node getNodePrevious(){ return this.nodePrevious; }
    public void setNodePrevious( Node nodePrevious ){ this.nodePrevious = nodePrevious; }
    public void resetNodePrevious(){ this.nodePrevious = null; }
    
    private LinkedHashMap< Node, Double > delayToNodeTarget = new LinkedHashMap< Node, Double >();
    public Iterator< Entry< Node, Double > > getIteratorDelayToNodeTarget(){ return this.delayToNodeTarget.entrySet().iterator(); }
    public double getDelayToNodeTarget( Node nodeTarget ){ return delayToNodeTarget.get( nodeTarget ); }
    public void putDelayToNodeTarget( Node nodeTarget, double delay ){ this.delayToNodeTarget.put( nodeTarget, delay ); }
	public void resetDelayToNodeTarget(){ this.delayToNodeTarget = new LinkedHashMap< Node, Double >(); }
    
	private double delayToNodeTargetMean = 0;
	public double getDelayToNodeTargetMean(){ return this.delayToNodeTargetMean; }
    public void setDelayToNodeTargetMean()
    {
    	this.delayToNodeTargetMean = 0;
    	
		SummaryStatistics sum = new SummaryStatistics();
		
		for( double delayToNodeTarget: this.delayToNodeTarget.values() )
			sum.addValue( delayToNodeTarget );

		this.delayToNodeTargetMean = sum.getMean();
    }
	
    private double delayToNodeTargetLongest = 0;
    public double getDelayToNodeTargetLongest(){ return delayToNodeTargetLongest; }
    public void setDelayToNodeTargetLongest()
    {
    	this.delayToNodeTargetLongest = 0;
    	
    	SummaryStatistics sum = new SummaryStatistics();
    	
    	for( double delayToNodeTarget: this.delayToNodeTarget.values() )
    		sum.addValue( delayToNodeTarget );
    	
    	this.delayToNodeTargetLongest = sum.getMax();
    }
    
    private LinkedHashMap< Node, List< Link > > linksToNodeTarget = new LinkedHashMap< Node, List< Link > >();;
    public List< Link > getLinksToNodeTarget( Node nodeTarget ){ return this.linksToNodeTarget.get( nodeTarget ); }
    public void putLinksToNodeTarget( Node nodeTarget, List< Link > links ){ this.linksToNodeTarget.put( nodeTarget, links ); }
	public void resetLinksToNodeTarget(){ this.linksToNodeTarget = new LinkedHashMap< Node, List< Link > >(); }
    
	private double numberPaths = 0;
	public double getNumberPaths(){ return this.numberPaths; }
    public void setNumberPaths()
    {
    	this.numberPaths = 0;
    	
		for( Link link: this.links )
			this.numberPaths += link.getNumberPaths();
    }
	
    // Control
    
	private boolean isSelected = false;
    public boolean isSelected(){ return this.isSelected; }
	public void setSelected( boolean isSelected ){ this.isSelected = isSelected; }
    
	private List< Node > nodesControlled = new ArrayList< Node >();
	public List< Node > getNodesControlled(){ return this.nodesControlled; }
	public void addNodeControlled( Node nodeToAdd ){ nodesControlled.add( nodeToAdd ); }
	
	private Node nodeController = null;
	public Node getNodeController(){ return nodeController; }
	public void setNodeController( Node nodeController ){ this.nodeController = nodeController; }
	
	// Allocation
	
    private double abw;
    public double getAbw(){ return this.abw;  }
    public void setAbw()
    {
    	this.abw = 0;
    	
    	SummaryStatistics sum = new SummaryStatistics();
    	
    	for( Link link: this.getLinks() )
    		sum.addValue( link.getBandwidthAvailable() );
    	
    	double numberLinks = this.getLinks().size();
    	this.abw = numberLinks * sum.getSum();
    }
	
	private List< VirtualNode > virtualNodes = new ArrayList< VirtualNode >();	
	public List< VirtualNode > getVirtualNodes(){ return this.virtualNodes; }
	public void allocate( VirtualNode virtualNode ){ this.virtualNodes.add( virtualNode ); }
	public void release( VirtualNode virtualNode ){ this.virtualNodes.remove( virtualNode ); }
    
	private double bandwidthOccupation;
	public double getBandwidthOccupation(){ return this.bandwidthOccupation; }
	public void setBandwidthOccupation()
	{
		SummaryStatistics sumBandwidthAvailable = new SummaryStatistics();
		SummaryStatistics sumBandwidthTotal = new SummaryStatistics();
		
		for( Link link: this.getLinks() )
		{
			sumBandwidthAvailable.addValue( link.getBandwidthAvailable() );
			sumBandwidthTotal.addValue( link.getBandwidthTotal() );
		}
		
		this.bandwidthOccupation = (sumBandwidthTotal.getSum() - sumBandwidthAvailable.getSum()) / sumBandwidthTotal.getSum();
	}

}
